//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <ddk.h>
#include <string.h>
#include <bootmods.h>

static const char *s_aUnremovableList[] = {
    "kernel.exe",
    "elastos.cfg",
    "encoding.dat",
};

typedef struct BMFile
{
    UInt32      uUAddr;
    UInt32      uPAddr;
    UInt32      uSize;
    UInt32      uAllocSize;
    UInt32      uRef;
    Boolean     bUnremovable;
} BMFile;

class BootModules : public CDeviceDriver
{
public:
    CARAPI Read(
        /* [in] */ Int64 u64Offset,
        /* [in] */ Int32 bytesToRead,
        /* [out] */ MemoryBuf * pBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Write(
        /* [in] */ Int64 u64Offset,
        /* [in] */ const MemoryBuf & buffer,
        /* [out] */ Int32 * pBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Control(
        /* [in] */ Handle32 nControlCode,
        /* [in] */ const MemoryBuf & inBuffer,
        /* [out] */ MemoryBuf * pOutBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);

    virtual void Dispose();

public:
    BootModules() { m_nCurrentIndex = -1; }

    ECode Init();
    ECode Reset();
    ECode MoveNext();
    ECode Current(BMFileInfo *);
    ECode Delete(UInt32 uIndex);
    ECode Find(char *pszName, UInt32 *pIndex);
    ECode Open(char *pszName, BMFileInfo *pInfo);
    ECode Close(UInt32 uIndex);

public:
    Int32         m_nCurrentIndex;

private:
    BMFile *m_pBMFile;
    DzMutex m_mutex;
};

#define INVALID_CURRENT_POS     -2

#define BOOT_MODULE_SIZE(m)     (m).size

ECode BootModules::Init()
{
    m_nCurrentIndex = -1;
    if (bsp.uNumberOfBootModules) {
        m_pBMFile = new BMFile[bsp.uNumberOfBootModules];
        if (!m_pBMFile) {
            return E_OUT_OF_MEMORY;
        }
        UInt32 uListCount = sizeof(s_aUnremovableList) / sizeof(char *);
        UInt32 i = 0, j = 0;
        for (i = 0; i < bsp.uNumberOfBootModules; i++) {
            m_pBMFile[i].uPAddr = (Address)
                    PAGENO_TO_PHYSADDR(bsp.pBootModules[i].region.uBasePageNo);
            m_pBMFile[i].uSize = (UInt32)bsp.pBootModules[i].size;
            m_pBMFile[i].uAllocSize = PAGE_SIZE *
                                 (bsp.pBootModules[i].region.uLimitPageNo
                                  - bsp.pBootModules[i].region.uBasePageNo);
            m_pBMFile[i].uUAddr =
                            _DzAllocBootModuleSpace(m_pBMFile[i].uAllocSize);
            if (!m_pBMFile[i].uUAddr) {
                delete[] m_pBMFile;
                m_pBMFile = NULL;
                return E_OUT_OF_MEMORY;
            }

            m_pBMFile[i].uRef = 0;
            m_pBMFile[i].bUnremovable = FALSE;
            for (j = 0; j < uListCount; j++) {
                if (_stricmp(s_aUnremovableList[j],
                    bsp.pBootModules[i].szName) == 0) {
                    m_pBMFile[i].bUnremovable = TRUE;
                }
            }
        }
    }

    return NOERROR;
}

ECode BootModules::Read(
    /* [in] */ Int64 u64Offset,
    /* [in] */ Int32 bytesToRead,
    /* [out] */ MemoryBuf * pBuffer,
    /* [out] */ IEvent **ppCompletionEvent)
{
    return E_ACCESS_DENIED;
}

ECode BootModules::Write(
    /* [in] */ Int64 u64Offset,
    /* [in] */ const MemoryBuf & buffer,
    /* [out] */ Int32 * pBytesWritten,
    /* [out] */ IEvent **ppCompletionEvent)
{
    return E_ACCESS_DENIED;
}

ECode BootModules::Reset()
{
    WaitResult wr;
    m_mutex.Lock(&wr);
    m_nCurrentIndex = -1;
    m_mutex.Unlock();
    return NOERROR;
}

ECode BootModules::MoveNext()
{
    WaitResult wr;
    m_mutex.Lock(&wr);
    if (INVALID_CURRENT_POS == m_nCurrentIndex) {
        m_mutex.Unlock();
        return E_INVALID_OPERATION;
    }
    m_nCurrentIndex++;

    while (m_nCurrentIndex < (Int32)bsp.uNumberOfBootModules
        && bsp.pBootModules[m_nCurrentIndex].szName[0] == '\0') {
        m_nCurrentIndex++;
    }

    if (m_nCurrentIndex >= (Int32)bsp.uNumberOfBootModules) {
        m_nCurrentIndex = INVALID_CURRENT_POS;
        m_mutex.Unlock();
        return S_FALSE;
    }
    m_mutex.Unlock();

    return NOERROR;
}

ECode BootModules::Current(BMFileInfo *pInfo)
{
    WaitResult wr;
    m_mutex.Lock(&wr);
    if (INVALID_CURRENT_POS == m_nCurrentIndex || m_nCurrentIndex == -1) {
        m_mutex.Unlock();
        return E_INVALID_OPERATION;
    }

    if (bsp.pBootModules[m_nCurrentIndex].szName[0] == '\0') {
        m_mutex.Unlock();
        return E_INVALID_OPERATION;
    }

    pInfo->uIndex = (UInt32)m_nCurrentIndex;
    pInfo->uUAddr = m_pBMFile[m_nCurrentIndex].uUAddr;
    pInfo->uSize = (UInt32)BOOT_MODULE_SIZE(bsp.pBootModules[m_nCurrentIndex]);
    strcpy(pInfo->szName, bsp.pBootModules[m_nCurrentIndex].szName);
    m_mutex.Unlock();
    return NOERROR;
}

ECode BootModules::Delete(UInt32 uIndex)
{
    if (m_pBMFile[uIndex].bUnremovable) {
        return E_FS_NO_PERMISSION;
    }

    WaitResult wr;
    m_mutex.Lock(&wr);

    if (uIndex >= bsp.uNumberOfBootModules
        || bsp.pBootModules[uIndex].szName[0] == '\0') {
        m_mutex.Unlock();
        return E_INVALID_ARGUMENT;
    }

    if (m_pBMFile[uIndex].uRef) {
        m_mutex.Unlock();
        return E_INVALID_OPERATION;
    }
    bsp.pBootModules[uIndex].szName[0] = '\0';

    _DzFreeBootModuleSpace(
            m_pBMFile[uIndex].uUAddr,
            m_pBMFile[uIndex].uAllocSize);

    ReclaimPages(
            bsp.pBootModules[uIndex].region.uBasePageNo,
            bsp.pBootModules[uIndex].region.uLimitPageNo);

    m_mutex.Unlock();

    return NOERROR;
}

ECode BootModules::Find(char *pszName, UInt32 *pIndex)
{
    if (pszName == NULL || '\0' == *pszName) {
        return E_INVALID_ARGUMENT;
    }

    for (int i = 0; i < (Int32)bsp.uNumberOfBootModules; i++) {
        if (_stricmp(pszName, bsp.pBootModules[i].szName) == 0) {
            *pIndex = i;
            return NOERROR;
        }
    }

    return E_FILE_NOT_FOUND;
}

ECode BootModules::Open(char *pszName, BMFileInfo *pInfo)
{
    assert(pInfo);
    UInt32 uIndex;

    WaitResult wr;
    m_mutex.Lock(&wr);
    ECode ec = Find(pszName, &uIndex);
    if (FAILED(ec)) {
        m_mutex.Unlock();
        return ec;
    }

    ec = _DzMapBootModule(m_pBMFile[uIndex].uUAddr,
                     m_pBMFile[uIndex].uAllocSize,
                     m_pBMFile[uIndex].uPAddr);

    if (FAILED(ec) && ec != E_ALREADY_EXIST) {
        m_mutex.Unlock();
        return ec;
    }

    pInfo->uIndex = uIndex;
    pInfo->uUAddr = m_pBMFile[uIndex].uUAddr;
    pInfo->uSize = m_pBMFile[uIndex].uSize;

    if (!m_pBMFile[uIndex].bUnremovable) {
        m_pBMFile[uIndex].uRef++;
    }
    m_mutex.Unlock();

    return NOERROR;
}

ECode BootModules::Close(UInt32 uIndex)
{
    WaitResult wr;
    m_mutex.Lock(&wr);

    if (uIndex >= bsp.uNumberOfBootModules
        || bsp.pBootModules[uIndex].szName[0] == '\0') {
        m_mutex.Unlock();
        return E_INVALID_ARGUMENT;
    }

    if (!m_pBMFile[uIndex].bUnremovable) {
        if (m_pBMFile[uIndex].uRef) {
            m_pBMFile[uIndex].uRef--;
        }
        _DzUnmapBootModule(m_pBMFile[uIndex].uUAddr,
                       m_pBMFile[uIndex].uAllocSize);
    }

    m_mutex.Unlock();

    return NOERROR;
}

ECode BootModules::Control(
    /* [in] */ Handle32 nControlCode,
    /* [in] */ const MemoryBuf & inBuffer,
    /* [out] */ MemoryBuf * pOutBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    switch (nControlCode) {
        case BMCommand_Reset:
            return this->Reset();

        case BMCommand_MoveNext:
            return this->MoveNext();

        case BMCommand_Current:
            if (pOutBuffer == NULL) {
                return E_INVALID_ARGUMENT;
            }
            pOutBuffer->SetUsed(pOutBuffer->GetCapacity());
            return this->Current((BMFileInfo *)pOutBuffer->GetPayload());

        case BMCommand_Delete:
            return this->Delete(*(UInt32 *)inBuffer.GetPayload());

        case BMCommand_Open:
            if (pOutBuffer == NULL) {
                return E_INVALID_ARGUMENT;
            }
            pOutBuffer->SetUsed(pOutBuffer->GetCapacity());
            return this->Open((char *)inBuffer.GetPayload(),
                            (BMFileInfo *)pOutBuffer->GetPayload());

        case BMCommand_Close:
            return this->Close(*(UInt32 *)inBuffer.GetPayload());

        default:
            return E_INVALID_OPTIONS;
    }
}

void BootModules::Dispose()
{
    if (m_pBMFile) {
        for (UInt32 i = 0; i < bsp.uNumberOfBootModules; i++) {
            if (bsp.pBootModules[i].szName[0] != '\0') {
                _DzFreeBootModuleSpace(m_pBMFile[i].uUAddr, m_pBMFile[i].uSize);
            }
        }
        delete[] m_pBMFile;
    }
    delete this;
}

EXTERN IDeviceDriver * CDECL CreateBootModules(uint_t uDeviceNo, void *pvParameter)
{
    BootModules *pBootModules = new BootModules();
    if (NULL == pBootModules) return NULL;
    if (FAILED(pBootModules->Init())) return NULL;
    pBootModules->AddRef();
    return pBootModules;
}
